home *** CD-ROM | disk | FTP | other *** search
/ Chip 2004 August / Chip_2004-08_cd1.bin / zkuste / fototools / download / phoa / phoa-setup.exe / {app} / API / phPhoa.pas < prev   
Pascal/Delphi Source File  |  2004-01-17  |  44KB  |  969 lines

  1. //*****************************************************************************
  2. //
  3. // PhoA file format description
  4. // The whole code (c)2002-2004 Dmitry Kann, except where otherwise explicitly
  5. // noted
  6. // Home sites:
  7. //   http://devtools.narod.ru/
  8. //   http://phoa.narod.ru/
  9. //
  10. // ATTENTION! None of this code can be reproduced in any form without prior
  11. // permission issued by the author.
  12. //
  13. // This unit describes general photo album file structure introduced in PhoA
  14. // picture arranging program in release 1.1.1a. This revision is known as
  15. // 'Revision 3' of .phoa file, and is the first one that supports so-called
  16. // chunk-based organization, and therefore is extensible. Revisions 1 and 2
  17. // were fixed-structure binary files, now they are proprietary PhoA formats
  18. // supported only by the program itself. Their specifications may be supplied
  19. // on an additional request.
  20. //
  21. // Contact email: phoa@narod.ru
  22. //
  23. // Target platform: Borland Delphi 7
  24. // Target OS:       Windows
  25. // Language:        Object Pascal
  26. //
  27. //*****************************************************************************
  28. unit phPhoa;
  29.  
  30. interface
  31. uses SysUtils, Windows, Classes;
  32.  
  33.    // 1. Introduction
  34.    // ===============
  35.    // Photo album files (phoa-files) are binary files accessible only with
  36.    // special programs. Native creator of .phoa is PhoA - picture arranging
  37.    // tool, which is available for free download at http://phoa.narod.ru/en
  38.    //
  39.    // Phoa-files contain various data on how pictures are arranged in the
  40.    // album. At the moment there are five major logical sections in the
  41.    // phoa-file data:
  42.    //
  43.    //  - File header
  44.    //  - General photo album data
  45.    //  - Pictures and their data as they appear in the phoa storage
  46.    //  - Groups hierarchy and links to the pictures
  47.    //  - Additional album data and structures
  48.    //
  49.    // 2. Photo album file sections
  50.    // ============================
  51.    // 2.1 File header
  52.    // ---------------
  53.    // Phoa-file always starts with ASCII string 'PhoA [PhotoAlbum] project
  54.    // file', represented by SPhoAFileSignature constant.
  55.    //
  56.    // Next to the signature follows Revision Number: 4-byte signed integer,
  57.    // Intel byte-order. Current Revision Number is the value of the
  58.    // IPhFileRevisionNumber constant.
  59.    //
  60.    // The header contents are constant and do not differ between revisions
  61.    // (except Revision Number itself). This allows reliable recognition
  62.    // of newer revisions, with subsequent denial of opening, and also
  63.    // allows to build an unified opening procedure (at least for header).
  64.    //
  65.    // WARNING:
  66.    // All information below refers to Revision 3 (see the header of this file
  67.    // for details). Next sections operate with chunks as data storage blocks.
  68.    //
  69.    // 2.2 General photo album data
  70.    // ----------------------------
  71.    // These data include common photo album data such as album description or
  72.    // thumbnail compression rate, as described by corresponding chunks.
  73.    //
  74.    // 2.3 Pictures and their data as they appear in the phoa storage
  75.    // --------------------------------------------------------------
  76.    // A planar list of pictures, enclosed with the list open/close-chunks
  77.    //
  78.    // 2.4 Groups hierarchy and links to the pictures
  79.    // ----------------------------------------------
  80.    // Hierarchically (recursively) organized group tree, each group maintaining
  81.    // list of child groups as well as list of picture IDs
  82.    //
  83.    // 2.5 Additional album data and structures
  84.    // ----------------------------------------
  85.    // Other photo album data, such as views
  86.    //
  87.    // These sections are pretty relative (except 2.1), and must be overseen in
  88.    // each case.
  89.    //
  90.    // 3. Chunks
  91.    // =========
  92.    // Starting with the section 2.2 (right after header), all phoa-file data
  93.    // are organized with chunks (Revision 3+ !)
  94.    // A CHUNK is an unsigned 2-byte Intel-ordered value, ranged from $0000 to
  95.    // $ffff, each value has its own predetermined meaning (if any), described
  96.    // in IPhChunk_xxxxxx constants. Each chunk has its own, fixed datatype,
  97.    // which can never be changed in later releases. So if you have read chunk's
  98.    // code, you can say for sure what datatype it contains. Moreover, each
  99.    // chunk is followed by one-byte datatype code, and then datatype-specific
  100.    // number of data.
  101.    //
  102.    // So, common chunk structure is as follows:
  103.    //
  104.    // 2 bytes                     Chunk code
  105.    // 1 byte                      Datatype code
  106.    // N bytes (datatype-specific) Chunk data
  107.    //
  108.    // 3.1 Chunk datatypes
  109.    // -------------------
  110.    // Code Name        Data length        Description
  111.    //                    (bytes)
  112.    // ---- --------  -----------------  --------------------------------------------------------------------------------
  113.    //    0 Empty            0           A chunk with no data
  114.    //    1 Byte             1           unsigned 1-byte (0..255)
  115.    //    2 Word             2           unsigned 2-byte (0..65535)
  116.    //    3 Int              4           signed 4-byte (-2147483648..2147483647)
  117.    //    4 StringB      1+(0..255)      Ansi string, with length Byte preceding (max length 255 bytes)
  118.    //    5 StringW     2+(0..65535)     Ansi string (not a *wide* string), with length Word preceding (max length 65 KB)
  119.    //    6 StringI   4+(0..2147483647)  Ansi string, with length Int preceding (max length 2 GB)
  120.    //
  121.    //  * Note on chunk data types:
  122.    //    Only Intel byte-order is used, where $12345678 is represented as byte
  123.    //    sequence: $78,$56,$34,$12.
  124.    //    Each chunk has its own, fixed datatype, which can never be changed in
  125.    //    later releases. Any mismatch with predefined chunk datatype must be
  126.    //    treated as error.
  127.    //
  128.    // 3.2 Chunk codes
  129.    // ---------------
  130.    // According to stated above, chunk code is an unsigned 2-byte number. The
  131.    // datatype and meaning of each chunk is described along with its
  132.    // declaration.
  133.    //
  134.    // Chunk codes $4000..$4fff are open-chunks for nested elements, always
  135.    // having Empty datatype. Each open-chunk has corresponding close-chunk
  136.    // with bit 15 set (ranged $c000..$cfff), eg:
  137.    //   $4000 (open) -> $c000 (close)
  138.    //   $4001 (open) -> $c001 (close)
  139.    // & so on. This allows correct recognition of close-chunks knowing none of
  140.    // the open-chunk purpose.
  141.    //
  142.    // 4. Common phoa-file Reader behaviour
  143.    // ====================================
  144.    // An algorithm designed for reading phoa-files (the Reader) must conform
  145.    // to the following rules:
  146.    //
  147.    // - The Reader reads file header:
  148.    //   o Signature must be exact copy of the PhoA file signature, any mismatch
  149.    //     leads to an error.
  150.    //   o Revision must be 3 or higher for the Reader to treat file as
  151.    //     chunk-based, otherwise this is not chunk-organized phoa-file, and
  152.    //     Reader must implement specific routines to handle it.
  153.    //   o Each specific Revision means possible INCOMPATIBILITIES compared to
  154.    //     lower Revision. So Reader must NOT read Revision it is not intended
  155.    //     to read. Though common file structure would be possibly leaved
  156.    //     intact, there might be some changes in chunk handling so the Reader
  157.    //     can only try to handle such file 'at its own risk'. Generally, this
  158.    //     format was designed just not to be updated frequently. The ability
  159.    //     to handle files with higher revisions is controlled by
  160.    //     StrictRevision conditional define, turn it off to let Reader to
  161.    //     continue with parsing file of 'wrong' revision.
  162.    // - In case Revision is 3 or higher (otherwise see note at the beginning of
  163.    //   this file):
  164.    //   o The Reader processes each chunk one-by-one. The main chunk principle
  165.    //     (and goal) is expansibility, so if the Reader encounters chunk not
  166.    //     known to it, it must just SKIP this chunk whole (along with its
  167.    //     data and/or nested chunks). The purpose of skipping chunk with its
  168.    //     nested chunks is that a chunk cannot be reliably recognized without
  169.    //     its context.
  170.    //     * For known chunk Reader then retrieves its stored datatype code and
  171.    //       does or does not verify this datatype. Generally, mismathing chunk
  172.    //       datatype may rise from broken phoa-files and must be treated as
  173.    //       error.
  174.    //     * If chunk is open-chunk (always with no data, see above), the Reader
  175.    //       handles all subsequent chunks till the terminating close-chunk as
  176.    //       nested (action depends on a particular case).
  177.    //     * If chunk is a data-chunk, it may or may not be processed, depending
  178.    //       on the Reader implementation.
  179.    //
  180.    // That is all for now. Yeah, quite long preface :)
  181.  
  182. {$DEFINE StrictRevision} // If defined, fail opening files with revisions higher than specified in this unit
  183.  
  184. type
  185.    // Possible chunk datatypes
  186.   TPhChunkDatatype = (pcdEmpty, pcdByte, pcdWord, pcdInt, pcdStringB, pcdStringW, pcdStringI);
  187.  
  188.   TPhChunkCode = Word;
  189.  
  190. const
  191.    // Phoa-file signature
  192.   SPhoAFileSignature               = 'PhoA [PhotoAlbum] project file'; // NEVER LOCALIZE!
  193.  
  194.    // Phoa-files revisions. Revision at index 0 is always the latest one
  195.   aPhFileRevisions: Array[0..2] of record
  196.     iNumber:  Integer; // Phoa-file Revision Number
  197.     sName:    String;  // Name of version family to recognize that revision
  198.     sMinName: String;  // Name of PhoA version introduced that revision
  199.   end = (
  200.     (iNumber: $0003; sName: 'PhoA 1.1+';  sMinName: '1.1.1a'),
  201.     (iNumber: $0002; sName: 'PhoA 1.0.x'; sMinName: '1.0.1a'),
  202.     (iNumber: $0001; sName: 'PhoA 0.x';   sMinName: '0.02b'));
  203.  
  204.    // Current photo album file Revision Number
  205.   IPhFileRevisionNumber            = $0003;
  206.    // Starting revision for chunk-based handling
  207.   IPhFile_MinChunkRevNumber        = $0003;
  208.  
  209.    //-------------------------------------------------------------------------------------------------------------------
  210.    // Chunk codes
  211.    //-------------------------------------------------------------------------------------------------------------------
  212.    // Base codes
  213.   IPhChunk_Open_Low                = $4000; // Low bound for all open-chunks
  214.   IPhChunk_Open_High               = $4fff; // High bound for all open-chunks
  215.   IPhChunk_Close_Low               = $c000; // Low bound for all close-chunks
  216.   IPhChunk_Close_High              = $cfff; // High bound for all close-chunks
  217.  
  218.    // General stuff
  219.   IPhChunk_Remark                  = $0000; // StringW  Any text, ignored by the Reader
  220.    // Common photo album data
  221.   IPhChunk_PhoaGenerator           = $1001; // StringB  Application created the album
  222.   IPhChunk_PhoaSavedDate           = $1002; // Int      Date file saved: number of days since Jan 01, 0001
  223.   IPhChunk_PhoaSavedTime           = $1003; // Int      Time file saved: number of seconds since midnight
  224.   IPhChunk_PhoaDescription         = $1010; // StringW  Photo album description text
  225.   IPhChunk_PhoaThumbQuality        = $1020; // Byte     Photo album thumbnail JPEG-quality level [1..100]
  226.   IPhChunk_PhoaThumbWidth          = $1021; // Word     Photo album thumbnail width  [32..1024]
  227.   IPhChunk_PhoaThumbHeight         = $1022; // Word     Photo album thumbnail height [32..1024]
  228.    // Picture properties
  229.   IPhChunk_Pic_ID                  = $1101; // Int      ID: an unique picture identifier, >=1
  230.   IPhChunk_Pic_ThumbnailData       = $1110; // StringI  Picture thumnail: JPEG data stream
  231.   IPhChunk_Pic_ThumbWidth          = $1111; // Word     Thumbnail width in pixels
  232.   IPhChunk_Pic_ThumbHeight         = $1112; // Word     Thumbnail height in pixels
  233.   IPhChunk_Pic_PicFileName         = $1120; // StringW  Absolute or relative (to the phoa-file) picture filename
  234.   IPhChunk_Pic_PicFileSize         = $1121; // Int      Picture file size, bytes
  235.   IPhChunk_Pic_PicWidth            = $1122; // Int      Image width, pixels
  236.   IPhChunk_Pic_PicHeight           = $1123; // Int      Image height, pixels
  237.   IPhChunk_Pic_PicFormat           = $1124; // Byte     Pixel image format ID (see below)
  238.   IPhChunk_Pic_Date                = $1130; // Int      Date: number of days since Jan 01, 0001
  239.   IPhChunk_Pic_Time                = $1131; // Int      Time: number of seconds since midnight
  240.   IPhChunk_Pic_Place               = $1132; // StringW  Place
  241.   IPhChunk_Pic_FilmNumber          = $1133; // StringW  Film number or name
  242.   IPhChunk_Pic_FrameNumber         = $1134; // StringB  Frame number
  243.   IPhChunk_Pic_Author              = $1135; // StringW  Picture author
  244.   IPhChunk_Pic_Media               = $1136; // StringW  Media name or code
  245.   IPhChunk_Pic_Desc                = $1137; // StringW  Description
  246.   IPhChunk_Pic_Notes               = $1138; // StringW  Notes
  247.   IPhChunk_Pic_Keywords            = $1139; // StringW  Keywords: Comma-separated list. Single entries containing spaces or commas must be double-quoted
  248.    // Picture group properties
  249.   IPhChunk_Group_Text              = $1201; // StringW  Group text (name)
  250.   IPhChunk_Group_Expanded          = $1202; // Byte     Group-node expanded flag (0/1)
  251.    // Picture linked in group properties
  252.   IPhChunk_GroupPic_ID             = $1220; // Int      Link to picture (the picture's ID)
  253.    // Photo album view properties
  254.   IPhChunk_View_Name               = $1301; // StringW  View name
  255.   IPhChunk_ViewGrouping_Prop       = $1310; // Word     View grouping property (see below)
  256.   IPhChunk_ViewGrouping_Unclass    = $1311; // Byte     View grouping switch: place unclassified pictures to a separate folder (0/1)
  257.   IPhChunk_ViewSorting_Prop        = $1320; // Word     View sorting property (see below)
  258.   IPhChunk_ViewSorting_Order       = $1321; // Byte     View sorting sort direction: 0=Ascending, 1=Descending
  259.    // Open-chunks
  260.   IPhChunk_Pics_Open               = $4010; // Empty    Open-chunk for photo album picture list
  261.   IPhChunk_Pic_Open                = $4011; // Empty    Open-chunk for single photo album picture entry (inside the list)
  262.   IPhChunk_Group_Open              = $4020; // Empty    Open-chunk for single picture group (root or nested)
  263.   IPhChunk_Groups_Open             = $4021; // Empty    Open-chunk for nested picture groups
  264.   IPhChunk_GroupPics_Open          = $4030; // Empty    Open-chunk for pics contained in the group
  265.   IPhChunk_Views_Open              = $4050; // Empty    Open-chunk for photo album views
  266.   IPhChunk_View_Open               = $4060; // Empty    Open-chunk for single photo album view
  267.   IPhChunk_ViewGroupings_Open      = $4061; // Empty    Open-chunk for photo album view groupings
  268.   IPhChunk_ViewGrouping_Open       = $4062; // Empty    Open-chunk for single photo album view grouping
  269.   IPhChunk_ViewSortings_Open       = $4063; // Empty    Open-chunk for photo album view sortings
  270.   IPhChunk_ViewSorting_Open        = $4064; // Empty    Open-chunk for single photo album view sorting
  271.    // Close-chunks
  272.   IPhChunk_Pics_Close              = $c010; // Empty    Close-chunk for photo album picture list
  273.   IPhChunk_Pic_Close               = $c011; // Empty    Close-chunk for single photo album picture entry (inside the list)
  274.   IPhChunk_Group_Close             = $c020; // Empty    Close-chunk for single picture group (root or nested)
  275.   IPhChunk_Groups_Close            = $c021; // Empty    Close-chunk for nested picture groups
  276.   IPhChunk_GroupPics_Close         = $c030; // Empty    Close-chunk for pics contained in the group
  277.   IPhChunk_Views_Close             = $c050; // Empty    Close-chunk for photo album views
  278.   IPhChunk_View_Close              = $c060; // Empty    Close-chunk for single photo album view
  279.   IPhChunk_ViewGroupings_Close     = $c061; // Empty    Close-chunk for photo album view groupings
  280.   IPhChunk_ViewGrouping_Close      = $c062; // Empty    Close-chunk for single photo album view grouping
  281.   IPhChunk_ViewSortings_Close      = $c063; // Empty    Close-chunk for photo album view sortings
  282.   IPhChunk_ViewSorting_Close       = $c064; // Empty    Close-chunk for single photo album view sorting
  283.  
  284. type
  285.    // Local chunk entry, used in aPhChunks[]
  286.   PPhChunkEntry = ^TPhChunkEntry;
  287.   TPhChunkEntry = record
  288.     wCode:     TPhChunkCode;     // Chunk code, one of the IPhChunk_xxxxxx constants
  289.     Datatype:  TPhChunkDatatype; // Predefined chunk datatype
  290.     iRangeMin: Integer;          // Low value limit (for ordinal values)
  291.     iRangeMax: Integer;          // High value limit (for ordinal values)
  292.   end;
  293.  
  294.    // List of chunks known for the moment, and their datatypes and ranges
  295. const
  296.   aPhChunks: Array[0..56] of TPhChunkEntry = (
  297.     (wCode: IPhChunk_Remark;                 Datatype: pcdStringW),
  298.     (wCode: IPhChunk_PhoaGenerator;          Datatype: pcdStringB),
  299.     (wCode: IPhChunk_PhoaSavedDate;          Datatype: pcdInt;    iRangeMin: 0;  iRangeMax: 3652058 {Dec 31, 9999}),
  300.     (wCode: IPhChunk_PhoaSavedTime;          Datatype: pcdInt;    iRangeMin: 0;  iRangeMax: 24*60*60),
  301.     (wCode: IPhChunk_PhoaDescription;        Datatype: pcdStringW),
  302.     (wCode: IPhChunk_PhoaThumbQuality;       Datatype: pcdByte;   iRangeMin: 1;  iRangeMax: 100),
  303.     (wCode: IPhChunk_PhoaThumbWidth;         Datatype: pcdWord;   iRangeMin: 32; iRangeMax: 1024),
  304.     (wCode: IPhChunk_PhoaThumbHeight;        Datatype: pcdWord;   iRangeMin: 32; iRangeMax: 1024),
  305.     (wCode: IPhChunk_Pic_ID;                 Datatype: pcdInt;    iRangeMin: 1;  iRangeMax: High(Integer)),
  306.     (wCode: IPhChunk_Pic_ThumbnailData;      Datatype: pcdStringI),
  307.     (wCode: IPhChunk_Pic_ThumbWidth;         Datatype: pcdWord;   iRangeMin: 32; iRangeMax: 1024),
  308.     (wCode: IPhChunk_Pic_ThumbHeight;        Datatype: pcdWord;   iRangeMin: 32; iRangeMax: 1024),
  309.     (wCode: IPhChunk_Pic_PicFileName;        Datatype: pcdStringW),
  310.     (wCode: IPhChunk_Pic_PicFileSize;        Datatype: pcdInt;    iRangeMin: 0;  iRangeMax: High(Integer)),
  311.     (wCode: IPhChunk_Pic_PicWidth;           Datatype: pcdInt;    iRangeMin: 0;  iRangeMax: High(Integer)),
  312.     (wCode: IPhChunk_Pic_PicHeight;          Datatype: pcdInt;    iRangeMin: 0;  iRangeMax: High(Integer)),
  313.     (wCode: IPhChunk_Pic_PicFormat;          Datatype: pcdByte;   iRangeMin: 0;  iRangeMax: 8),
  314.     (wCode: IPhChunk_Pic_Date;               Datatype: pcdInt;    iRangeMin: 0;  iRangeMax: 3652058 {Dec 31, 9999}),
  315.     (wCode: IPhChunk_Pic_Time;               Datatype: pcdInt;    iRangeMin: 0;  iRangeMax: 24*60*60),
  316.     (wCode: IPhChunk_Pic_Place;              Datatype: pcdStringW),
  317.     (wCode: IPhChunk_Pic_FilmNumber;         Datatype: pcdStringW),
  318.     (wCode: IPhChunk_Pic_FrameNumber;        Datatype: pcdStringB),
  319.     (wCode: IPhChunk_Pic_Author;             Datatype: pcdStringW),
  320.     (wCode: IPhChunk_Pic_Media;              Datatype: pcdStringW),
  321.     (wCode: IPhChunk_Pic_Desc;               Datatype: pcdStringW),
  322.     (wCode: IPhChunk_Pic_Notes;              Datatype: pcdStringW),
  323.     (wCode: IPhChunk_Pic_Keywords;           Datatype: pcdStringW),
  324.     (wCode: IPhChunk_Group_Text;             Datatype: pcdStringW),
  325.     (wCode: IPhChunk_Group_Expanded;         Datatype: pcdByte;   iRangeMin: 0;  iRangeMax: 1),
  326.     (wCode: IPhChunk_GroupPic_ID;            Datatype: pcdInt;    iRangeMin: 1;  iRangeMax: High(Integer)),
  327.     (wCode: IPhChunk_View_Name;              Datatype: pcdStringW),
  328.     (wCode: IPhChunk_ViewGrouping_Prop;      Datatype: pcdWord;   iRangeMin: 0;  iRangeMax: 10),
  329.     (wCode: IPhChunk_ViewGrouping_Unclass;   Datatype: pcdByte;   iRangeMin: 0;  iRangeMax: 1),
  330.     (wCode: IPhChunk_ViewSorting_Prop;       Datatype: pcdWord;   iRangeMin: 0;  iRangeMax: 19),
  331.     (wCode: IPhChunk_ViewSorting_Order;      Datatype: pcdByte;   iRangeMin: 0;  iRangeMax: 1),
  332.     (wCode: IPhChunk_Pics_Open;              Datatype: pcdEmpty),
  333.     (wCode: IPhChunk_Pic_Open;               Datatype: pcdEmpty),
  334.     (wCode: IPhChunk_Group_Open;             Datatype: pcdEmpty),
  335.     (wCode: IPhChunk_Groups_Open;            Datatype: pcdEmpty),
  336.     (wCode: IPhChunk_GroupPics_Open;         Datatype: pcdEmpty),
  337.     (wCode: IPhChunk_Views_Open;             Datatype: pcdEmpty),
  338.     (wCode: IPhChunk_View_Open;              Datatype: pcdEmpty),
  339.     (wCode: IPhChunk_ViewGroupings_Open;     Datatype: pcdEmpty),
  340.     (wCode: IPhChunk_ViewGrouping_Open;      Datatype: pcdEmpty),
  341.     (wCode: IPhChunk_ViewSortings_Open;      Datatype: pcdEmpty),
  342.     (wCode: IPhChunk_ViewSorting_Open;       Datatype: pcdEmpty),
  343.     (wCode: IPhChunk_Pics_Close;             Datatype: pcdEmpty),
  344.     (wCode: IPhChunk_Pic_Close;              Datatype: pcdEmpty),
  345.     (wCode: IPhChunk_Group_Close;            Datatype: pcdEmpty),
  346.     (wCode: IPhChunk_Groups_Close;           Datatype: pcdEmpty),
  347.     (wCode: IPhChunk_GroupPics_Close;        Datatype: pcdEmpty),
  348.     (wCode: IPhChunk_Views_Close;            Datatype: pcdEmpty),
  349.     (wCode: IPhChunk_View_Close;             Datatype: pcdEmpty),
  350.     (wCode: IPhChunk_ViewGroupings_Close;    Datatype: pcdEmpty),
  351.     (wCode: IPhChunk_ViewGrouping_Close;     Datatype: pcdEmpty),
  352.     (wCode: IPhChunk_ViewSortings_Close;     Datatype: pcdEmpty),
  353.     (wCode: IPhChunk_ViewSorting_Close;      Datatype: pcdEmpty));
  354.  
  355.    // Possible values for Pixel Format are (see note below):
  356.    //
  357.    // Value  Default  Description
  358.    // -----  -------  --------------
  359.    //   0             Device-dependent
  360.    //   1             1-bit
  361.    //   2             4-bit
  362.    //   3             8-bit
  363.    //   4             15-bit
  364.    //   5             16-bit
  365.    //   6             24-bit
  366.    //   7             32-bit
  367.    //   8       *     Custom or unknown
  368.    //
  369.    // Possible values for Grouping Property are (see note below):
  370.    //
  371.    // Value  Description
  372.    // -----  --------------
  373.    //   0    Picture file path
  374.    //   1    Date year
  375.    //   2    Date month
  376.    //   3    Date day
  377.    //   4    Time hour
  378.    //   5    Time minute
  379.    //   6    Place
  380.    //   7    Film number
  381.    //   8    Author
  382.    //   9    Media name/code
  383.    //  10    Keywords
  384.    //
  385.    // Possible values for Sorting Property are (see note below):
  386.    //
  387.    // Value  Description
  388.    // -----  --------------
  389.    //   0    ID
  390.    //   1    Picture filename
  391.    //   2    Picture filename with path
  392.    //   3    Picture file path
  393.    //   4    Picture file size
  394.    //   5    Picture file size in bytes (for sorting is just the same as 'Picture file size')
  395.    //   6    Image width
  396.    //   7    Image height
  397.    //   8    Image dimensions
  398.    //   9    Pixel format
  399.    //  10    Date
  400.    //  11    Time
  401.    //  12    Place
  402.    //  13    Film number
  403.    //  14    Frame number
  404.    //  15    Author
  405.    //  16    Description
  406.    //  17    Notes
  407.    //  18    Media
  408.    //  19    Keywords (keywords are always ordered alphabetically, case-insensitively)
  409.    //
  410.    // --------
  411.    // * Note on 'enumerated' values: the Reader should IGNORE values with
  412.    //     unknown code, this allows to extend specifications in future. The
  413.    //     'Default' value is just the one used for initializing (may be helpful
  414.    //     in such case), if applicable.
  415.  
  416.    // TPhoaStreamer status constants
  417.   IPhStatus_OK                     =  0; // Succeeded
  418.   IPhStatus_InvalidMode            =  1; // Invalid opening mode (trying to write in read mode and vice versa)
  419.   IPhStatus_CannotRead             =  2; // Stream read error
  420.   IPhStatus_CannotWrite            =  3; // Stream write error
  421.   IPhStatus_CannotAlterRevision    =  4; // Trying to modify Revision Number after some data have been written
  422.   IPhStatus_InvalidSignature       =  5; // Invalid phoa-file signature
  423.   IPhStatus_NotAChunkedFile        =  6; // File being open is not a chunk-based one, cannot be handled with this unit
  424.   IPhStatus_FileRevNewer           =  7; // File being open has higher revision than possible with this unit (was created with the newer program version)
  425.   IPhStatus_UnknownChunkToWrite    =  8; // Code of a chunk is unknown
  426.   IPhStatus_WrongDatatypePassed    =  9; // Wrong datatype passed to a WriteChunkxxxx() procedure
  427.   IPhStatus_InvalidDatatype        = 10; // Chunk datatype invalid or unknown
  428.  
  429. type
  430.    //-------------------------------------------------------------------------------------------------------------------
  431.    // Basic implementation of I/O routines (btw used in PhoA)
  432.    //-------------------------------------------------------------------------------------------------------------------
  433.  
  434.    // Base Exception class
  435.   EPhoaStreamerError = class(Exception)
  436.   private
  437.     FErrorCode: Integer;
  438.   public
  439.     constructor Create(const Msg: String; iErrCode: Integer);
  440.     constructor CreateFmt(const Msg: String; const Args: Array of const; iErrCode: Integer);
  441.      // Props
  442.      // -- Code of error encountered, one of the IPhStatus_xxxxxx constants
  443.     property ErrorCode: Integer read FErrorCode;
  444.   end;
  445.  
  446.   TPhoaStreamingMode = (psmRead, psmWrite);
  447.  
  448.    // Possible ReadChunk result
  449.   TPhReadingChunkResult = (
  450.     rcrOK,               // No errors
  451.     rcrUnknown,          // Chunk code is unknown
  452.     rcrInvalidDatatype,  // Datatype invalid or unknown (out of range)
  453.     rcrDatatypeMismatch, // Datatype mismatch
  454.     rcrEOF);             // No more chunks (end of file encountered)
  455.  
  456.    // Base class for storing/reading photo album data to/from a stream
  457.   TPhoaStreamer = class(TObject)
  458.   private
  459.      // Prop storage
  460.     FStream: TStream;
  461.     FMode: TPhoaStreamingMode;
  462.     FTransferredBytes: Cardinal;
  463.     FRevisionNumber: Integer;
  464.     FBasePath: String;
  465.      // Prop handlers
  466.     procedure SetRevisionNumber(Value: Integer);
  467.     function  GetChunked: Boolean;
  468.   protected
  469.      // Writes specified number of bytes from Buffer to the file
  470.     procedure Write(const Buffer; iSize: Integer);
  471.      // Reads specified number of bytes from the file into Buffer
  472.     procedure Read(var Buffer; iSize: Integer);
  473.      // Raises an exception if RequiredMode<>Mode
  474.     procedure CheckMode(RequiredMode: TPhoaStreamingMode);
  475.      // Checking header data validity, virtual to have possibility to alter behaviour in a descendant
  476.     procedure ValidateSignature(const sReadSignature: String); virtual;
  477.     procedure ValidateRevision; virtual;
  478.   public
  479.     constructor Create(AStream: TStream; AMode: TPhoaStreamingMode; const sBasePath: String);
  480.      // Writing/reading routines for typed data
  481.     procedure WriteByte(b: Byte);
  482.     procedure WriteWord(w: Word);
  483.     procedure WriteInt(i: Integer);
  484.     procedure WriteStringB(const s: String);
  485.     procedure WriteStringW(const s: String);
  486.     procedure WriteStringI(const s: String);
  487.     function  ReadByte: Byte;
  488.     function  ReadWord: Word;
  489.     function  ReadInt: Integer;
  490.     function  ReadStringB: String;
  491.     function  ReadStringW: String;
  492.     function  ReadStringI: String;
  493.      // Writes/reads file header
  494.     procedure WriteHeader;
  495.     procedure ReadHeader;
  496.      // Writing/reading chunks (NO DATA are being written/read, only chunk code and datatype!)
  497.      // -- Version with auto-detecting chunk datatype
  498.     procedure WriteChunk(Code: TPhChunkCode); overload;
  499.      // -- Version with forced chunk datatype
  500.     procedure WriteChunk(Code: TPhChunkCode; Datatype: TPhChunkDatatype); overload;
  501.      // -- Version writing typed values (strict datatype)
  502.     procedure WriteChunkByte(Code: TPhChunkCode; b: Byte);
  503.     procedure WriteChunkWord(Code: TPhChunkCode; w: Word);
  504.     procedure WriteChunkInt(Code: TPhChunkCode; i: Integer);
  505.     procedure WriteChunkString(Code: TPhChunkCode; const s: String); // Auto-detecting type
  506.      // -- Reads chunk code and datatype
  507.     function  ReadChunk(out Code: TPhChunkCode; out Datatype: TPhChunkDatatype): TPhReadingChunkResult;
  508.      // -- Reads chunk code, datatype and value. Value is being read only if result is not rcrInvalidDatatype or rcrEOF,
  509.      //    invalid datatype raises exception. bSkipUnknown controls whether to skip unknown chunks (including all nested,
  510.      //    if necessary). bSkipUnmatched controls whether to skip chunks which datatype mismatches from predefined one
  511.     function  ReadChunkValue(out Code: TPhChunkCode; out Datatype: TPhChunkDatatype; var vValue: Variant; bSkipUnknown, bSkipUnmatched: Boolean): TPhReadingChunkResult;
  512.      // Skips all chunks until close-chunk for OpenCode chunk code encountered. May be used for ignoring unknown
  513.      //   open-chunks. It is valid to call SkipNestedChunks for non-open-chunks (ignored if it's the case)
  514.     procedure SkipNestedChunks(OpenCode: TPhChunkCode);
  515.      // Props
  516.      // -- True if chunk-based revision used
  517.     property Chunked: Boolean read GetChunked;
  518.      // -- Photo album file path (for translating relative picture file paths)
  519.     property BasePath: String read FBasePath;
  520.      // -- Mode in which the object was created
  521.     property Mode: TPhoaStreamingMode read FMode;
  522.      // -- PhoA Revision Number, readonly in Read mode; can only be modified before any data are written
  523.     property RevisionNumber: Integer read FRevisionNumber write SetRevisionNumber;
  524.      // -- Stream used for transferring data
  525.     property Stream: TStream read FStream;
  526.      // -- Number of bytes read or written from/to the stream
  527.     property TransferredBytes: Cardinal read FTransferredBytes;
  528.   end;
  529.  
  530.    // Class for storing/reading photo album data to/from a file
  531.   TPhoaFiler = class(TPhoaStreamer)
  532.   private
  533.     FFilename: String;
  534.   public
  535.     constructor Create(AMode: TPhoaStreamingMode; const sFilename: String);
  536.     destructor Destroy; override;
  537.      // Props
  538.      // -- Open file name
  539.     property Filename: String read FFilename;
  540.   end;
  541.  
  542.    //-------------------------------------------------------------------------------------------------------------------
  543.    // Utility routines
  544.    //-------------------------------------------------------------------------------------------------------------------
  545.  
  546.    // Searches aPhChunks[] for a chunk code, returns pointer to entry if found, otherwise nil.
  547.   function FindChunk(Code: TPhChunkCode): PPhChunkEntry;
  548.    // The same as FindChunk(), but never returns nil; if Code not found, raises exception
  549.   function FindChunkStrict(Code: TPhChunkCode): PPhChunkEntry;
  550.    // Checking whether chunk Code is an open-chunk
  551.   function IsOpenChunk(Code: TPhChunkCode): Boolean;
  552.    // Checking whether chunk Code is close-chunk for chunk OpenCode
  553.   function IsCloseChunk(Code, OpenCode: TPhChunkCode): Boolean;
  554.  
  555.    // Date and time conversion
  556.    // Converts date into number of days passed since Jan 01, 0001
  557.   function  DateToPhoaDate(const Date: TDateTime): Integer;
  558.    // Converts time into number of seconds passed since midnight
  559.   function  TimeToPhoaTime(const Time: TDateTime): Integer;
  560.    // Converts phoa-date (days since Jan 01, 0001) into TDateTime type
  561.   function  PhoaDateToDate(iDate: Integer): TDateTime;
  562.    // Converts phoa-time (number of seconds since midnight) into TDateTime type
  563.   function  PhoaTimeToTime(iTime: Integer): TDateTime;
  564.  
  565. resourcestring
  566.    // Error messages
  567.   SPhStreamErr_InvalidMode         = 'Invalid opening mode';
  568.   SPhStreamErr_CannotRead          = 'Cannot read %d bytes from the stream';
  569.   SPhStreamErr_CannotWrite         = 'Cannot write %d bytes to the stream';
  570.   SPhStreamErr_CannotAlterRevision = 'RevisionNumber cannot be modified after some data have been written';
  571.   SPhStreamErr_InvalidSinature     = 'Invalid file signature';
  572.   SPhStreamErr_NotAChunkedFile     = 'File is not chunk-based (old format)';
  573.   SPhStreamErr_FileRevNewer        = 'File was created by the program version newer than this. The file cannot be loaded';
  574.   SPhStreamErr_UnknownChunkToWrite = 'Unknown chunk code to write (%d)';
  575.   SPhStreamErr_WrongDatatypePassed = 'Wrong Datatype of chunk passed to WriteChunkxxxxxx() (%s needed)';
  576.   SPhStreamErr_InvalidDatatype     = 'Chunk datatype invalid or unknown (code: %d)';
  577.  
  578. implementation
  579. uses Math, Variants;
  580.  
  581.   function FindChunk(Code: TPhChunkCode): PPhChunkEntry;
  582.   var i: Integer;
  583.   begin
  584.     for i := 0 to High(aPhChunks) do begin
  585.       Result := @aPhChunks[i];
  586.       if Result^.wCode=Code then Exit;
  587.     end;
  588.     Result := nil;
  589.   end;
  590.  
  591.   function FindChunkStrict(Code: TPhChunkCode): PPhChunkEntry;
  592.   begin
  593.     Result := FindChunk(Code);
  594.     if Result=nil then
  595.       raise EPhoaStreamerError.CreateFmt(SPhStreamErr_UnknownChunkToWrite, [Code], IPhStatus_UnknownChunkToWrite);
  596.   end;
  597.  
  598.   function IsOpenChunk(Code: TPhChunkCode): Boolean;
  599.   begin
  600.     Result := (Code>=IPhChunk_Open_Low) and (Code<=IPhChunk_Open_High);
  601.   end;
  602.  
  603.   function IsCloseChunk(Code, OpenCode: TPhChunkCode): Boolean;
  604.   begin
  605.     Result := (Code>=IPhChunk_Close_Low) and (Code<=IPhChunk_Close_High) and (Code=OpenCode or $8000);
  606.   end;
  607.  
  608.   function DateToPhoaDate(const Date: TDateTime): Integer;
  609.   begin
  610.     Result := Trunc(Date)-Trunc(EncodeDate(0001, 01, 01));
  611.   end;
  612.  
  613.   function TimeToPhoaTime(const Time: TDateTime): Integer;
  614.   begin
  615.     Result := Trunc(24*60*60*Frac(Time));
  616.   end;
  617.  
  618.   function PhoaDateToDate(iDate: Integer): TDateTime;
  619.   begin
  620.     Result := iDate+EncodeDate(0001, 01, 01);
  621.   end;
  622.  
  623.   function PhoaTimeToTime(iTime: Integer): TDateTime;
  624.   begin
  625.     Result := Frac(iTime/(24*60*60));
  626.   end;
  627.  
  628.    // Raises 'Wrong Datatype passed' exception
  629.   procedure WrongWriteChunkDatatype(const sRequiredName: String);
  630.   begin
  631.     raise EPhoaStreamerError.CreateFmt(SPhStreamErr_WrongDatatypePassed, [sRequiredName], IPhStatus_WrongDatatypePassed);
  632.   end;
  633.  
  634.    //-------------------------------------------------------------------------------------------------------------------
  635.    // EPhoaStreamerError
  636.    //-------------------------------------------------------------------------------------------------------------------
  637.  
  638.   constructor EPhoaStreamerError.Create(const Msg: String; iErrCode: Integer);
  639.   begin
  640.     inherited Create(Msg);
  641.     FErrorCode := iErrCode;
  642.   end;
  643.  
  644.   constructor EPhoaStreamerError.CreateFmt(const Msg: String; const Args: Array of const; iErrCode: Integer);
  645.   begin
  646.     inherited CreateFmt(Msg, Args);
  647.     FErrorCode := iErrCode;
  648.   end;
  649.  
  650.    //-------------------------------------------------------------------------------------------------------------------
  651.    // TPhoaStreamer
  652.    //-------------------------------------------------------------------------------------------------------------------
  653.  
  654.   procedure TPhoaStreamer.CheckMode(RequiredMode: TPhoaStreamingMode);
  655.   begin
  656.     if FMode<>RequiredMode then raise EPhoaStreamerError.Create(SPhStreamErr_InvalidMode, IPhStatus_InvalidMode);
  657.   end;
  658.  
  659.   constructor TPhoaStreamer.Create(AStream: TStream; AMode: TPhoaStreamingMode; const sBasePath: String);
  660.   begin
  661.     inherited Create;
  662.     FStream         := AStream;
  663.     FMode           := AMode;
  664.     FBasePath       := sBasePath;
  665.     FRevisionNumber := IPhFileRevisionNumber; // Assume most modern revision by default
  666.   end;
  667.  
  668.   function TPhoaStreamer.GetChunked: Boolean;
  669.   begin
  670.     Result := FRevisionNumber>=IPhFile_MinChunkRevNumber;
  671.   end;
  672.  
  673.   procedure TPhoaStreamer.Read(var Buffer; iSize: Integer);
  674.   begin
  675.     CheckMode(psmRead);
  676.     if FStream.Read(Buffer, iSize)<>iSize then
  677.       raise EPhoaStreamerError.CreateFmt(SPhStreamErr_CannotRead, [iSize], IPhStatus_CannotRead);
  678.     Inc(FTransferredBytes, iSize);
  679.   end;
  680.  
  681.   function TPhoaStreamer.ReadByte: Byte;
  682.   begin
  683.     Read(Result, SizeOf(Result));
  684.   end;
  685.  
  686.   function TPhoaStreamer.ReadChunk(out Code: TPhChunkCode; out Datatype: TPhChunkDatatype): TPhReadingChunkResult;
  687.   var pe: PPhChunkEntry;
  688.   begin
  689.      // Check if there are data at all
  690.     if FStream.Position>=FStream.Size then begin
  691.       Code     := 0;
  692.       Datatype := pcdEmpty;
  693.       Result   := rcrEOF;
  694.      // Read the chunk and its datatype
  695.     end else begin
  696.       Code     := ReadWord;
  697.       Datatype := TPhChunkDatatype(ReadByte);
  698.        // Try to find a chunk
  699.       pe := FindChunk(Code);
  700.        // Validate Datatype
  701.       if not (Datatype in [Low(Datatype)..High(Datatype)]) then Result := rcrInvalidDatatype
  702.        // If not found
  703.       else if pe=nil then Result := rcrUnknown
  704.        // Compare Datatype
  705.       else if Datatype<>pe.Datatype then Result := rcrDatatypeMismatch
  706.        // Ok
  707.       else Result := rcrOK;
  708.     end;
  709.   end;
  710.  
  711.   function TPhoaStreamer.ReadChunkValue(out Code: TPhChunkCode; out Datatype: TPhChunkDatatype; var vValue: Variant; bSkipUnknown, bSkipUnmatched: Boolean): TPhReadingChunkResult;
  712.   var pe: PPhChunkEntry;
  713.   begin
  714.     vValue := Null;
  715.     repeat
  716.       Result := ReadChunk(Code, Datatype);
  717.       case Result of
  718.         rcrInvalidDatatype:  raise EPhoaStreamerError.CreateFmt(SPhStreamErr_InvalidDatatype, [Byte(Datatype)], IPhStatus_InvalidDatatype);
  719.         rcrEOF:              Break;
  720.       end;
  721.        // Read the value
  722.       case Datatype of
  723.         pcdByte:    vValue := ReadByte;
  724.         pcdWord:    vValue := ReadWord;
  725.         pcdInt:     vValue := ReadInt;
  726.         pcdStringB: vValue := ReadStringB;
  727.         pcdStringW: vValue := ReadStringW;
  728.         pcdStringI: vValue := ReadStringI;
  729.       end;
  730.        // Validate ranges for ordinal types. Outranged values assume unmatched
  731.       if (Result=rcrOK) and (Datatype in [pcdByte, pcdWord, pcdInt]) then begin
  732.         pe := FindChunk(Code);
  733.         if (vValue<pe.iRangeMin) or (vValue>pe.iRangeMax) then Result := rcrDatatypeMismatch;
  734.       end;
  735.        // Check the chunk for validity
  736.       case Result of
  737.         rcrOK:               Break;
  738.         rcrUnknown:          if not bSkipUnknown then Break;
  739.         rcrDatatypeMismatch: if not bSkipUnmatched then Break;
  740.       end;
  741.        // Chunk is to be skipped, if we're here. Check if it's nested one
  742.       SkipNestedChunks(Code);
  743.     until False;
  744.   end;
  745.  
  746.   procedure TPhoaStreamer.ReadHeader;
  747.   var s: String;
  748.   begin
  749.      // Load and check signature
  750.     SetLength(s, Length(SPhoAFileSignature));
  751.     Read(s[1], Length(s));
  752.     ValidateSignature(s);
  753.      // Read Revision Number
  754.     FRevisionNumber := ReadInt;
  755.     ValidateRevision;
  756.   end;
  757.  
  758.   function TPhoaStreamer.ReadInt: Integer;
  759.   begin
  760.     Read(Result, SizeOf(Result));
  761.   end;
  762.  
  763.   function TPhoaStreamer.ReadStringB: String;
  764.   var b: Byte;
  765.   begin
  766.     b := ReadByte;
  767.     SetLength(Result, b);
  768.     Read(Result[1], b);
  769.   end;
  770.  
  771.   function TPhoaStreamer.ReadStringI: String;
  772.   var i: Integer;
  773.   begin
  774.     i := ReadInt;
  775.     SetLength(Result, i);
  776.     Read(Result[1], i);
  777.   end;
  778.  
  779.   function TPhoaStreamer.ReadStringW: String;
  780.   var w: Word;
  781.   begin
  782.     w := ReadWord;
  783.     SetLength(Result, w);
  784.     Read(Result[1], w);
  785.   end;
  786.  
  787.   function TPhoaStreamer.ReadWord: Word;
  788.   begin
  789.     Read(Result, SizeOf(Result));
  790.   end;
  791.  
  792.   procedure TPhoaStreamer.SetRevisionNumber(Value: Integer);
  793.   begin
  794.     CheckMode(psmWrite);
  795.     if FTransferredBytes>0 then
  796.       raise EPhoaStreamerError.Create(SPhStreamErr_CannotAlterRevision, IPhStatus_CannotAlterRevision);
  797.     FRevisionNumber := Value;
  798.   end;
  799.  
  800.   procedure TPhoaStreamer.SkipNestedChunks(OpenCode: TPhChunkCode);
  801.   var
  802.     wCode: TPhChunkCode;
  803.     Datatype: TPhChunkDatatype;
  804.     vValue: Variant;
  805.   begin
  806.     if IsOpenChunk(OpenCode) then
  807.       repeat
  808.         if ReadChunkValue(wCode, Datatype, vValue, True, True)=rcrEOF then Break;
  809.          // If nested-chunk-structure encountered, recurse it
  810.         SkipNestedChunks(wCode);
  811.       until IsCloseChunk(wCode, OpenCode);
  812.   end;
  813.  
  814.   procedure TPhoaStreamer.ValidateRevision;
  815.   begin
  816.      // Check that we are dealing with chunk-based file
  817.     if not Chunked then raise EPhoaStreamerError.Create(SPhStreamErr_NotAChunkedFile, IPhStatus_NotAChunkedFile);
  818.     {$IFDEF StrictRevision}
  819.      // Check that RevisionNumber is 'normal' for proper handling
  820.     if FRevisionNumber>IPhFileRevisionNumber then
  821.       raise EPhoaStreamerError.Create(SPhStreamErr_FileRevNewer, IPhStatus_FileRevNewer);
  822.     {$ENDIF StrictRevision}
  823.   end;
  824.  
  825.   procedure TPhoaStreamer.ValidateSignature(const sReadSignature: String);
  826.   begin
  827.     if sReadSignature<>SPhoAFileSignature then
  828.       raise EPhoaStreamerError.Create(SPhStreamErr_InvalidSinature, IPhStatus_InvalidSignature);
  829.   end;
  830.  
  831.   procedure TPhoaStreamer.Write(const Buffer; iSize: Integer);
  832.   begin
  833.     CheckMode(psmWrite);
  834.     if FStream.Write(Buffer, iSize)<>iSize then
  835.       raise EPhoaStreamerError.CreateFmt(SPhStreamErr_CannotWrite, [iSize], IPhStatus_CannotWrite);
  836.     Inc(FTransferredBytes, iSize);
  837.   end;
  838.  
  839.   procedure TPhoaStreamer.WriteByte(b: Byte);
  840.   begin
  841.     Write(b, SizeOf(b));
  842.   end;
  843.  
  844.   procedure TPhoaStreamer.WriteChunk(Code: TPhChunkCode);
  845.   begin
  846.     WriteChunk(Code, FindChunkStrict(Code)^.Datatype);
  847.   end;
  848.  
  849.   procedure TPhoaStreamer.WriteChunk(Code: TPhChunkCode; Datatype: TPhChunkDatatype);
  850.   begin
  851.     WriteWord(Code);
  852.     WriteByte(Byte(Datatype));
  853.   end;
  854.  
  855.   procedure TPhoaStreamer.WriteChunkByte(Code: TPhChunkCode; b: Byte);
  856.   var pe: PPhChunkEntry;
  857.   begin
  858.      // Find chunk entry
  859.     pe := FindChunkStrict(Code);
  860.     if pe.Datatype<>pcdByte then WrongWriteChunkDatatype('Byte');
  861.      // Write chunk/datatype code
  862.     WriteChunk(Code, pcdByte);
  863.      // Write chunk data
  864.     WriteByte(b);
  865.   end;
  866.  
  867.   procedure TPhoaStreamer.WriteChunkInt(Code: TPhChunkCode; i: Integer);
  868.   var pe: PPhChunkEntry;
  869.   begin
  870.      // Find chunk entry
  871.     pe := FindChunkStrict(Code);
  872.     if pe.Datatype<>pcdInt then WrongWriteChunkDatatype('Int');
  873.      // Write chunk/datatype code
  874.     WriteChunk(Code, pcdInt);
  875.      // Write chunk data
  876.     WriteInt(i);
  877.   end;
  878.  
  879.   procedure TPhoaStreamer.WriteChunkString(Code: TPhChunkCode; const s: String);
  880.   var pe: PPhChunkEntry;
  881.   begin
  882.      // Find chunk entry
  883.     pe := FindChunkStrict(Code);
  884.     if not (pe.Datatype in [pcdStringB, pcdStringW, pcdStringI]) then WrongWriteChunkDatatype('StringB, StringW or StringI');
  885.      // Write chunk/datatype code
  886.     WriteChunk(Code, pe.Datatype);
  887.      // Write chunk data
  888.     case pe.Datatype of
  889.       pcdStringB: WriteStringB(s);
  890.       pcdStringW: WriteStringW(s);
  891.       pcdStringI: WriteStringI(s);
  892.     end;
  893.   end;
  894.  
  895.   procedure TPhoaStreamer.WriteChunkWord(Code: TPhChunkCode; w: Word);
  896.   var pe: PPhChunkEntry;
  897.   begin
  898.      // Find chunk entry
  899.     pe := FindChunkStrict(Code);
  900.     if pe.Datatype<>pcdWord then WrongWriteChunkDatatype('Word');
  901.      // Write chunk/datatype code
  902.     WriteChunk(Code, pcdWord);
  903.      // Write chunk data
  904.     WriteWord(w);
  905.   end;
  906.  
  907.   procedure TPhoaStreamer.WriteHeader;
  908.   var s: String;
  909.   begin
  910.      // Write signature
  911.     s := SPhoAFileSignature;
  912.     Write(s[1], Length(s));
  913.      // Write Revision Number
  914.     WriteInt(FRevisionNumber);
  915.   end;
  916.  
  917.   procedure TPhoaStreamer.WriteInt(i: Integer);
  918.   begin
  919.     Write(i, SizeOf(i));
  920.   end;
  921.  
  922.   procedure TPhoaStreamer.WriteStringB(const s: String);
  923.   var b: Byte;
  924.   begin
  925.     b := Min(High(b), Length(s));
  926.     WriteByte(b);
  927.     if b>0 then Write(s[1], b);
  928.   end;
  929.  
  930.   procedure TPhoaStreamer.WriteStringI(const s: String);
  931.   var i: Integer;
  932.   begin
  933.     i := Length(s);
  934.     WriteInt(i);
  935.     if i>0 then Write(s[1], i);
  936.   end;
  937.  
  938.   procedure TPhoaStreamer.WriteStringW(const s: String);
  939.   var w: Word;
  940.   begin
  941.     w := Min(High(w), Length(s));
  942.     WriteWord(w);
  943.     if w>0 then Write(s[1], w);
  944.   end;
  945.  
  946.   procedure TPhoaStreamer.WriteWord(w: Word);
  947.   begin
  948.     Write(w, SizeOf(w));
  949.   end;
  950.  
  951.    //-------------------------------------------------------------------------------------------------------------------
  952.    // TPhoaFiler
  953.    //-------------------------------------------------------------------------------------------------------------------
  954.  
  955.   constructor TPhoaFiler.Create(AMode: TPhoaStreamingMode; const sFilename: String);
  956.   const aFileMode: Array[TPhoaStreamingMode] of Word = (fmOpenRead or fmShareDenyWrite, fmCreate);
  957.   begin
  958.     inherited Create(TFileStream.Create(sFilename, aFileMode[AMode]), AMode, ExtractFilePath(sFilename));
  959.     FFilename := sFilename;
  960.   end;
  961.  
  962.   destructor TPhoaFiler.Destroy;
  963.   begin
  964.     Stream.Free;
  965.     inherited Destroy;
  966.   end;
  967.  
  968. end.
  969.